package com.myKng.content.service.impl;

import com.myKng.content.model.File;
import com.myKng.content.repository.FileRepository;
import com.myKng.content.service.FileService;
import io.minio.*;
import io.minio.errors.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
@Slf4j
public class FileServiceImpl implements FileService {

    private final FileRepository fileRepository;
    private final MinioClient minioClient;
    
    @Value("${minio.bucket}")
    private String bucket;
    
    @Value("${minio.endpoint}")
    private String endpoint;
    
    @Value("${file.upload.allowed-types}")
    private String allowedFileTypes;
    
    /**
     * 检查文件类型是否允许上传
     * @param contentType 文件类型
     * @return 是否允许
     */
    private boolean isAllowedFileType(String contentType) {
        if (StringUtils.isBlank(contentType)) {
            return false;
        }
        List<String> allowedTypes = Arrays.asList(allowedFileTypes.split(","));
        return allowedTypes.contains(contentType);
    }
    
    /**
     * 生成文件的MD5哈希值
     * @param inputStream 文件输入流
     * @return MD5哈希值
     */
    private String generateFileMd5(InputStream inputStream) throws IOException {
        try {
            return DigestUtils.md5DigestAsHex(inputStream);
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }
    
    @Override
    @Transactional
    @CacheEvict(value = "files", allEntries = true)
    public File uploadFile(MultipartFile multipartFile, String uploadedBy) {
        try {
            // 获取文件信息
            String originalFilename = multipartFile.getOriginalFilename();
            String contentType = multipartFile.getContentType();
            long fileSize = multipartFile.getSize();
            
            // 检查文件类型是否允许
            if (!isAllowedFileType(contentType)) {
                throw new IllegalArgumentException("File type not allowed: " + contentType);
            }
            
            // 生成唯一文件ID
            String fileId = UUID.randomUUID().toString();
            String extension = FilenameUtils.getExtension(originalFilename);
            
            // 构建MinIO对象名称
            String objectName = fileId + "/" + originalFilename;
            
            log.info("Uploading file: {}, size: {}, type: {}", originalFilename, fileSize, contentType);
            
            // 上传文件到MinIO
            minioClient.putObject(
                PutObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .stream(multipartFile.getInputStream(), fileSize, -1)
                    .contentType(contentType)
                    .build()
            );
            
            // 构建文件访问URL
            String fileUrl = endpoint + "/" + bucket + "/" + objectName;
            
            // 保存文件元数据到数据库
            File file = File.builder()
                    .originalName(originalFilename)
                    .fileId(fileId)
                    .fileType(contentType)
                    .fileUrl(fileUrl)
                    .fileSize(fileSize)
                    .uploadedBy(uploadedBy)
                    .build();
            
            log.info("File uploaded successfully: {}", fileId);
            return fileRepository.save(file);
            
        } catch (IOException | ServerException | InsufficientDataException | ErrorResponseException | 
                NoSuchAlgorithmException | InvalidKeyException | InvalidResponseException | 
                XmlParserException | InternalException e) {
            log.error("Failed to upload file: {}", e.getMessage(), e);
            throw new RuntimeException("Failed to upload file: " + e.getMessage(), e);
        }
    }
    
    @Override
    @Cacheable(value = "files", key = "#fileId")
    public Optional<File> getFileById(String fileId) {
        log.debug("Fetching file with ID: {}", fileId);
        return fileRepository.findByFileId(fileId);
    }
    
    @Override
    @Transactional
    @CacheEvict(value = "files", key = "#fileId")
    public void deleteFile(String fileId) {
        log.info("Deleting file with ID: {}", fileId);
        Optional<File> fileOptional = fileRepository.findByFileId(fileId);
        
        if (fileOptional.isPresent()) {
            File file = fileOptional.get();
            
            try {
                // 从MinIO删除文件
                String objectName = fileId + "/" + file.getOriginalName();
                minioClient.removeObject(
                    RemoveObjectArgs.builder()
                        .bucket(bucket)
                        .object(objectName)
                        .build()
                );
                
                // 从数据库删除文件记录
                fileRepository.delete(file);
                log.info("File deleted successfully: {}", fileId);
                
            } catch (ServerException | InsufficientDataException | ErrorResponseException | 
                    NoSuchAlgorithmException | InvalidKeyException | InvalidResponseException | 
                    XmlParserException | InternalException | IOException e) {
                log.error("Failed to delete file: {}", e.getMessage(), e);
                throw new RuntimeException("Failed to delete file: " + e.getMessage(), e);
            }
        } else {
            log.warn("File not found with id: {}", fileId);
            throw new RuntimeException("File not found with id: " + fileId);
        }
    }
    
    @Override
    @Cacheable(value = "files_by_user", key = "#uploadedBy + '_' + #pageable.pageNumber + '_' + #pageable.pageSize")
    public Page<File> getFilesByUser(String uploadedBy, Pageable pageable) {
        log.debug("Fetching files for user: {}, page: {}", uploadedBy, pageable.getPageNumber());
        return fileRepository.findByUploadedBy(uploadedBy, pageable);
    }
    
    @Override
    @Cacheable(value = "files_by_type", key = "#fileType + '_' + #pageable.pageNumber + '_' + #pageable.pageSize")
    public Page<File> getFilesByType(String fileType, Pageable pageable) {
        log.debug("Fetching files by type: {}, page: {}", fileType, pageable.getPageNumber());
        return fileRepository.findByFileType(fileType, pageable);
    }
    
    @Override
    public Page<File> searchFilesByName(String fileName, Pageable pageable) {
        log.debug("Searching files by name: {}, page: {}", fileName, pageable.getPageNumber());
        return fileRepository.findByOriginalNameContaining(fileName, pageable);
    }
    
    @Override
    @Cacheable(value = "files_by_ids", key = "#fileIds.toString()")
    public List<File> getFilesByIds(List<String> fileIds) {
        log.debug("Fetching files by ids: {}", fileIds);
        return fileRepository.findByFileIdIn(fileIds);
    }
    
    /**
     * 生成文件的临时预览URL
     * @param fileId 文件ID
     * @param expiryTime 过期时间（秒）
     * @return 临时访问URL
     */
    public String generatePreviewUrl(String fileId, int expiryTime) {
        try {
            Optional<File> fileOptional = getFileById(fileId);
            
            if (fileOptional.isPresent()) {
                File file = fileOptional.get();
                String objectName = fileId + "/" + file.getOriginalName();
                
                // 生成临时访问URL
                String url = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(bucket)
                        .object(objectName)
                        .expiry(expiryTime, TimeUnit.SECONDS)
                        .build()
                );
                
                log.debug("Generated preview URL for file: {}, expires in {} seconds", fileId, expiryTime);
                return url;
            } else {
                log.warn("File not found for preview: {}", fileId);
                throw new RuntimeException("File not found with id: " + fileId);
            }
        } catch (ServerException | InsufficientDataException | ErrorResponseException | 
                NoSuchAlgorithmException | InvalidKeyException | InvalidResponseException | 
                XmlParserException | InternalException | IOException e) {
            log.error("Failed to generate preview URL: {}", e.getMessage(), e);
            throw new RuntimeException("Failed to generate preview URL: " + e.getMessage(), e);
        }
    }
}